﻿Imports System.ComponentModel

''' <summary>
''' 只能输入数字，可以定制小数位
''' 及
''' 取精度方式：截断或四舍五入
''' </summary>
Public Class DataGridViewCurrencyTextBoxColumn
    Inherits DataGridViewTextBoxColumn

    Private m_Precision As Int16 '小数精度
    Private m_PrecisionStyle As PrecisionStyle '取精度方式

    Public Sub New()

        Me.CellTemplate = New DataGridViewCurrencyTextBoxCell()
        m_Precision = 2 '默认为2位小数
        m_PrecisionStyle = PrecisionStyle.PrecisionCut '默认为截取精度 

        Me.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight
    End Sub

    Public Overrides Property CellTemplate As DataGridViewCell
        Get
            Return MyBase.CellTemplate
        End Get
        Set(value As DataGridViewCell)
            If (value IsNot Nothing) AndAlso
                Not value.GetType().IsAssignableFrom(GetType(DataGridViewCurrencyTextBoxCell)) _
                Then
                Throw New InvalidCastException("Must be a DataGridViewMultiColumnComboBoxCell")
            End If
            MyBase.CellTemplate = value
        End Set
    End Property

    <System.ComponentModel.Browsable(True), System.ComponentModel.Description("设置小数位精度，整数表示。默认为2。")>
    Public Property Precision As Short
        Get
            Return m_Precision
        End Get
        Set(value As Short)
            m_Precision = value
        End Set
    End Property

    <System.ComponentModel.Browsable(True), System.ComponentModel.Description("取小数精度方式，枚举类型，默认为PrecisionStyle.PrecisionCut截断。")>
    Public Property PrecisionStyle As PrecisionStyle
        Get
            Return m_PrecisionStyle
        End Get
        Set(value As PrecisionStyle)
            m_PrecisionStyle = value
        End Set
    End Property
End Class

'小数精度的截断方式
Public Enum PrecisionStyle
    PrecisionCut = 0
    PrecisionRound = 1
End Enum

Public Class DataGridViewCurrencyTextBoxCell
    Inherits DataGridViewTextBoxCell

    '单元格编辑控件的类型
    Private Shared defaultEditType As Type = GetType(DataGridViewCurrencyTextBoxEditingControl)
    '单元格保存的值类型
    Private Shared defaultValueType As Type = GetType(Decimal)

    '缓存单元格的值
    Private m_DecimalPlaces As Decimal

    Public Sub New()
        Me.m_DecimalPlaces = 0D '默认值
    End Sub

    'Public Property DecimalPlaces As Decimal
    '    Get
    '        Return Me.m_DecimalPlaces
    '    End Get
    '    Set(value As Decimal)
    '        If m_DecimalPlaces <> value Then

    '            SetDecimalPlaces(Me.RowIndex, value)
    '            'TODO:待添加过程
    '            'OnCommonChange(); // 确保需要时单元格或表格列重绘制及自动调整大小
    '        End If
    '    End Set
    'End Property

    'Friend Sub SetDecimalPlaces(ByVal rowIndex As Integer, ByVal value As Decimal)
    '    Me.m_DecimalPlaces = value
    '    'TODO:等添加函数
    '    'If (OwnsEditingNumericUpDown(rowIndex)) Then
    '    '            {
    '    '    this.EditingNumericUpDown.DecimalPlaces = value;
    '    '}
    'End Sub


    ''' <summary>
    ''' 单元格值类型，该单元格保存的数据应为Decimal
    ''' </summary>
    ''' <returns></returns>
    Public Overrides ReadOnly Property ValueType As Type
        Get
            Dim mybaseValueType As Type = MyBase.ValueType

            If mybaseValueType IsNot Nothing Then
                Return mybaseValueType
            End If

            Return defaultValueType

        End Get
    End Property

    Public Overrides Function Clone() As Object
        'Clone父类后，添加新的成员

        Dim DataGridViewCell As DataGridViewCurrencyTextBoxCell = CType(MyBase.Clone(), DataGridViewCurrencyTextBoxCell)

        If DataGridViewCell IsNot Nothing Then
            DataGridViewCell.m_DecimalPlaces = Me.m_DecimalPlaces
        End If

        Return DataGridViewCell

    End Function

    'KeyEntersEditMode(KeyEventArgs)方法
    '具有复杂编辑行为的单元格类型（即使用编辑控件的单元格）需要重写该方法来确定哪些击键激发编辑控件的显示
    '（当表格的EditMode属性是EditOnKeystrokeOrF2时）。对于DataGridViewNumericUpDownCell类型，数字和负号
    '（应该包括小数点——译者注）将激活编辑控件。
    Public Overrides Function KeyEntersEditMode(ByVal e As KeyEventArgs) As Boolean
        'Dim numberFormatInfo As System.Globalization.NumberFormatInfo = System.Globalization.CultureInfo.CurrentCulture.NumberFormat
        Dim negativeSignKey As Keys = Keys.None
        'Dim negativeSignStr As String = numberFormatInfo.NegativeSign

        '如果负号不空，则取出负号所对应的键
        'If Not String.IsNullOrEmpty(negativeSignKey) And negativeSignStr.Length = 1 Then
        'negativeSignKey = CType(VkKeyscanA(negativeSignStr(0)), Keys)
        'negativeSignKey = (Keys)(VkKeyScan(negativeSignStr[0]))
        'End If
        negativeSignKey = Keys.OemMinus '任何国家或地区的减号键

        If (Char.IsDigit(Chr(e.KeyCode)) Or
                (e.KeyCode >= Keys.NumPad0 And e.KeyCode <= Keys.NumPad9) Or
                 negativeSignKey = e.KeyCode Or
                Keys.Subtract = e.KeyCode
            ) And
                 (Not e.Shift) And (Not e.Alt) And (Not e.Control) Then

            Return True '如果是上述键，则需要激活编辑控件进行编辑
        End If

        Return False '其他键不需要考虑激活编辑控件

    End Function

    '当表格控件需要提供单元格值的格式化显示时，将调用该方法。对于DataGridViewNumericUpDownCell，
    'Value是System.Decimal类型、FormattedValue是System.String类型。因此，该方法需要转换decimal为字符串。
    '基类执行这个转换，但DataGridViewNumericUpDownCell仍然需要重写其默认行为，
    '确保返回的字符串完全符合NumericUpDown控件需要的显示形式。
    ''' <summary>
    ''' 表格控件需要提供单元格值的格式化显示时，将调用该方法。
    ''' </summary>
    ''' <param name="value"></param>
    ''' <param name="rowIndex"></param>
    ''' <param name="cellStyle"></param>
    ''' <param name="valueTypeConverter"></param>
    ''' <param name="formattedValueTypeConverter"></param>
    ''' <param name="context"></param>
    ''' <returns></returns>
    Protected Overrides Function GetFormattedValue(value As Object, rowIndex As Integer, ByRef cellStyle As DataGridViewCellStyle, valueTypeConverter As TypeConverter, formattedValueTypeConverter As TypeConverter, context As DataGridViewDataErrorContexts) As Object
        '默认情况下，基类会转换
        Dim FormattedValue As Object = MyBase.GetFormattedValue(value, rowIndex, cellStyle, valueTypeConverter, formattedValueTypeConverter, context)
        Dim returnValueString As String = TryCast(FormattedValue, String)
        Dim ctl As DataGridViewCurrencyTextBoxColumn = TryCast(Me.OwningColumn, DataGridViewCurrencyTextBoxColumn)

        Dim pre As Int16 = ctl.Precision
        Dim preStyle As PrecisionStyle = ctl.PrecisionStyle

        Dim formatString As String = "{0:#,###."
        For i = 0 To pre - 1
            formatString &= "0"
        Next

        formatString &= "}"

        If returnValueString IsNot Nothing And value IsNot Nothing Then
            Return GetFormattedString(formatString, returnValueString, preStyle, pre)
        Else
            Return FormattedValue
        End If
    End Function

    ''' <summary>
    ''' 返回单元格的编辑控件类型
    ''' </summary>
    ''' <returns></returns>
    Public Overrides ReadOnly Property EditType As Type
        Get
            '必须返回 cell 拥有的编辑控件类型
            Return defaultEditType
        End Get
    End Property


    'InitializeEditingControl

    Public Overrides Sub InitializeEditingControl(rowIndex As Integer, initialFormattedValue As Object, dataGridViewCellStyle As DataGridViewCellStyle)
        '此处必须让父类处理，即父类的处理逻辑先走完
        MyBase.InitializeEditingControl(rowIndex, initialFormattedValue, dataGridViewCellStyle)

        '取出当前正在编辑的控件，转换为 calendarEditingControl
        Dim ctl As DataGridViewCurrencyTextBoxEditingControl = CType(Me.DataGridView.EditingControl, DataGridViewCurrencyTextBoxEditingControl)
        Dim col As DataGridViewCurrencyTextBoxColumn = CType(OwningColumn, DataGridViewCurrencyTextBoxColumn)

        If ctl Is Nothing Then
            Return
        End If

        '此处设置不允许输入中文
        ctl.ImeMode = ImeMode.Close
        ctl.TabStop = False
        ctl.TextAlign = HorizontalAlignment.Right


        Dim numS As String = initialFormattedValue.ToString

        Dim formatString As String

        If String.IsNullOrEmpty(numS) Then
            ctl.Text = ""
        Else

            'Debug.Print("Initialize: me.value=" & Me.Value)
            'numS = Me.Value.ToString
            formatString = "{0:#,###."
            For i = 0 To col.Precision - 1
                formatString &= "0"
            Next
            formatString &= "}"

            ctl.Text = GetFormattedString(formatString, numS, col.PrecisionStyle, col.Precision)

            'ctl.Text = numS

            'Debug.Print("Cell - InitializeEditingControl: text=" & numS)
        End If

        'MyBase.InitializeEditingControl(rowIndex, numS, dataGridViewCellStyle)

    End Sub

    Private Function GetFormattedString(ByVal format As String, ByVal v As String, ByVal preStyle As PrecisionStyle, ByVal pre As Int16) As String
        Dim dotIndex As Int16
        Dim numS As String = v

        Select Case preStyle
            Case PrecisionStyle.PrecisionCut
                dotIndex = numS.IndexOf("."c)
                If dotIndex = -1 Then
                    numS = String.Format(format, Decimal.Parse(numS))
                Else
                    Dim subLen As Int16
                    subLen = dotIndex + pre + 1
                    subLen = IIf(subLen > numS.Length, numS.Length, subLen)
                    numS = numS.Substring(0, subLen)
                    numS = String.Format(format, Decimal.Parse(numS))
                End If
            Case PrecisionStyle.PrecisionRound
                numS = String.Format(format, Decimal.Round(Decimal.Parse(numS), pre))
        End Select

        Return numS

    End Function
End Class

Public Class DataGridViewCurrencyTextBoxEditingControl
    Inherits DataGridViewTextBoxEditingControl

    'Private isInputChar As Boolean = True

    Private valueIsChanged As Boolean = False

    Public Sub New()
        MyBase.New()

        Me.TextAlign = HorizontalAlignment.Right
        'Me.TabStop = False '不能是tab循环的一部分

        'Me.ImeMode = ImeMode.Close

        'Debug.Print("editingControl - new")
    End Sub



    Protected Overrides Sub OnKeyPress(e As KeyPressEventArgs)
        MyBase.OnKeyPress(e)
        '如果是特殊键，不处理
        If Asc(e.KeyChar) <= 32 Or Asc(e.KeyChar） > 126 Then
            Return
        End If


        '如果是负号

        '如果非数字键，放弃输入
        'If (Not Char.IsDigit(e.KeyChar)) And (Asc(e.KeyChar) <> Keys.OemMinus) And (Asc(e.KeyChar) <> Keys.Subtract) And (Asc(e.KeyChar) <> Keys.Decimal) Then
        Dim keyCode As Integer = Asc(e.KeyChar)

        If (Not Char.IsDigit(e.KeyChar)) And (e.KeyChar <> "-") And (e.KeyChar <> ".") Then
            e.Handled = True
            Return
        End If

        '如果是小数点
        '判断当前是否已经有了小数点
        If e.KeyChar = "." AndAlso Me.Text.IndexOf(".") <> -1 Then
            e.Handled = True
            Return
        End If

        '如果是负号
        If e.KeyChar = "-" AndAlso Me.Text.IndexOf("-") <> -1 Then
            e.Handled = True
            Return
        End If

    End Sub

    Public Overrides Property EditingControlFormattedValue As Object
        Get
            If String.IsNullOrEmpty(Me.Text) Then
                Return ""
            Else
                Debug.Print("EditingControlFormattedValue -GET - text=" & Me.Text)
                Return GetFormatedValue(Me.Text).ToString
            End If

            'Return MyBase.EditingControlFormattedValue
        End Get
        Set(value As Object)
            If String.IsNullOrEmpty(value) Then
                Me.Text = ""
            Else
                Me.Text = GetFormatedValue(value)
            End If

            valueIsChanged = True
            EditingControlDataGridView.NotifyCurrentCellDirty(True)

            'Debug.Print("Property: formattedValue -SET - value=" & value)
            MyBase.EditingControlFormattedValue = value
        End Set
    End Property


    Public Overrides Property EditingControlValueChanged As Boolean
        Get
            'Return MyBase.EditingControlValueChanged
            'Debug.Print("EditingControlValueChanged -GET  此时值 为：" & Me.Text)
            Return valueIsChanged
        End Get
        Set(value As Boolean)
            'Debug.Print("EditingControlValueChanged - SET: value=" & value & " 此时值 为：" & Me.Text)
            Me.Text = GetFormatedValue(Me.Text)
            valueIsChanged = True
            'Debug.Print("EditingControlValueChanged - SET: value=" & value & " 更新后值 为：" & Me.Text)
            'MyBase.EditingControlValueChanged = value
            EditingControlDataGridView.NotifyCurrentCellDirty(True)
        End Set
    End Property

    ''' <summary>
    ''' 处理显示的格式化值
    ''' </summary>
    ''' <param name="context"></param>
    ''' <returns></returns>
    Public Overrides Function GetEditingControlFormattedValue(context As DataGridViewDataErrorContexts) As Object

        'Return Me.Text
        'Debug.Print("GetEditingControlFormattedValue - formattedvalue : text = " & Me.Text)
        Return Me.Text
        'Return MyBase.GetEditingControlFormattedValue(context)
    End Function

    Private Function GetFormatedValue(ByVal s As String) As String
        '取出并判断当前所在列的精度设定
        Dim myCell As DataGridViewCurrencyTextBoxCell = TryCast(Me.EditingControlDataGridView.CurrentCell, DataGridViewCurrencyTextBoxCell)
        Dim myCol As DataGridViewCurrencyTextBoxColumn = Nothing

        'Debug.Print("GetFormatedValue:s = " & s)
        If myCell Is Nothing Then
            Return 0
        End If

        myCol = myCell.OwningColumn
        If myCol Is Nothing Then
            Return 0
        End If

        Dim dotIndex As Int16
        Dim formatString As String = "{0:#,###."
        For i = 0 To myCol.Precision - 1
            formatString &= "0"
        Next
        formatString &= "}"

        Dim numS As String = Nothing
        Dim numD As Decimal
        Decimal.TryParse(s, numD)

        Select Case myCol.PrecisionStyle
            Case PrecisionStyle.PrecisionCut
                dotIndex = s.IndexOf("."c)
                If dotIndex = -1 Then
                    'numD = Decimal.Round(Decimal.Parse(s), myCol.Precision)
                    numS = String.Format(formatString, numD)
                Else
                    Dim subLen As Int16
                    subLen = dotIndex + myCol.Precision + 1
                    subLen = IIf(subLen > s.Length, s.Length, subLen)
                    numS = s.Substring(0, subLen)

                    numS = String.Format(formatString, numD)
                    'numD = Decimal.Parse(numS)
                End If
            Case PrecisionStyle.PrecisionRound
                numS = String.Format(formatString, Decimal.Round(numD, myCol.Precision))
        End Select

        'Debug.Print("格式化后：numS=" & numS)
        Return numS

    End Function
End Class
